#include "conn_tab.h"

static connection_t* connTablePtr = NULL;
static int lastIndex = 0;

static const actionCell_t synSent = { 
	.syn = 1,
	.ack = 1,
	.fin = 0,
	.rst = 0,	
	.side = SERVER,
	.nextState = TCP_CONN_SYN_ACK,	
};

static const actionCell_t synAck = { 
	.syn = 0,
	.ack = 0,
	.fin = 0,
	.rst = 0,	
	.side = BOTH,
	.nextState = TCP_CONN_ESTAB,	
};

static const actionCell_t connEst = { 
	.syn = 0,
	.ack = 0,
	.fin = 1, // 
	.rst = 0,	
	.side = BOTH,
	.nextState = TCP_CONN_CLOSING,	
};

static const actionCell_t connClosing = { 
	.syn = 0,
	.ack = 1,
	.fin = 1, // 
	.rst = 0,	
	.side = BOTH,
	.nextState = TCP_CONN_CLOSING,	
};

static actionCell_t tcpActionTable[NUM_OF_STATES];

#define BAD_CELL {BAD_STATE, SERVER}

static cell_t tcpStateMachine[NUM_OF_TCP_STATES][NUM_OF_COMMANDS] = {
				/*SYN	           | SYN_ACK           | DATA               |CLIENT_FIN             |SERVER_FIN         |FIN_ACK           |RESET    */
/*PRE_START*/  {SYN_SENT,CLIENT},      BAD_CELL,            BAD_CELL,        BAD_CELL,            BAD_CELL,              BAD_CELL,          {PRE_START,BOTH},
/*SYN_SENT*/    BAD_CELL,          {CONN_SYN_ACK,SERVER},   BAD_CELL,        BAD_CELL,            BAD_CELL,              BAD_CELL,          {SYN_SENT,BOTH},
/*CONN_SYN_ACK*/BAD_CELL,            	BAD_CELL,       {CONN_ESTAB,BOTH},   BAD_CELL,            BAD_CELL,              BAD_CELL,          {CONN_SYN_ACK,BOTH},   
/*CONN_ESTAB*/	BAD_CELL,             BAD_CELL,             BAD_CELL,      {CLOSE_CLIENT,CLIENT},{CLOSE_SERVER,SERVER},  BAD_CELL,          {CONN_ESTAB,BOTH},
/*CLOSE_CLIENT*/BAD_CELL,             BAD_CELL,        {CLOSE_CLIENT,SERVER},BAD_CELL,            BAD_CELL,             {FIN_ACK,SERVER},   {CLOSE_CLIENT,BOTH},
/*CLOSE_SERVER*/BAD_CELL,             BAD_CELL,        {CLOSE_SERVER,CLIENT},BAD_CELL,            BAD_CELL,             {FIN_ACK,CLIENT},   {CLOSE_SERVER,BOTH},
/*CLOSED*/	    BAD_CELL,             BAD_CELL,             BAD_CELL,        BAD_CELL,            BAD_CELL,              BAD_CELL,          {CLOSED,BOTH},	
};	


connection_t* findConversation(rawPacket_t* packet){
	int i = 0;
	connection_t* tempPtr = connTablePtr;
	for(;i<CONNECTION_TABLE_ENTRIES;i++,tempPtr++){
		if((tempPtr->cli_ip == packet->source_ip && tempPtr->cli_port == packet->source_port && tempPtr->ser_ip == packet->dest_ip && tempPtr->ser_port == packet->dest_port) || 
		   (tempPtr->cli_ip == packet->dest_ip && tempPtr->cli_port == packet->dest_port && tempPtr->ser_ip == packet->source_ip && tempPtr->ser_port == packet->source_port)){
			   if(tempPtr->expires < get_seconds())
					return CONV_EXPIRED;
			   return tempPtr;
		   }		
	}
	return CONV_NOT_FOUND;
}

int addConversation(rawPacket_t* packet){
	int i = 0;
	int found = 0;
	unsigned long now = get_seconds();	
	for(i=0;i<CONNECTION_TABLE_ENTRIES;i++){
		if(connTablePtr[i].expires < now){
			found = 1;
			break;
		}
	}	
	if(!found)
		return -1; //REASON_CONNECTION_TABLE_FULL
	// place found
	// copy data	
	connTablePtr[i].cli_ip = packet->source_ip;
	connTablePtr[i].cli_port = packet->source_port;
	connTablePtr[i].ser_ip = packet->dest_ip;
	connTablePtr[i].cli_port = packet->dest_port;
	connTablePtr[i].state = SYN_SENT;
	connTablePtr[i].expires = get_seconds() + 5;	
	return 1;
}

tcp_states_t nextState(tcp_states_t currentState, tcp_commands_t command, side_t side){
	if(currentState < PRE_START || currentState > CLOSED){ 
		printk("\ncurrentState out of bounds\n");
		return BAD_STATE;
	}
	if(command < SYN || command > RESET){ 
		printk("\command out of bounds\n");
		return BAD_STATE;
	}
	if(tcpStateMachine[currentState][command].side == side || tcpStateMachine[currentState][command].side == BOTH)
		return tcpStateMachine[currentState][command].nextState;
	return BAD_STATE;
		
}

tcp_commands_t getCommand(tcpHeaderParams_t params){
	if(!params.syn && !params.ack && !params.fin && !params.rst)
		return DATA;
	if(params.syn && !params.ack && !params.fin && !params.rst)
		return SYN;
	if(params.syn && params.ack && !params.fin && !params.rst)
		return SYN_ACK;	
	if(!params.syn && !params.ack && params.fin && !params.rst){
		if(params.side == SERVER) return SERVER_FIN;
		if(params.side == CLIENT) return CLIENT_FIN;				
	}
	if(!params.syn && params.ack && params.fin && !params.rst)
		return FIN_ACK;
	if(!params.syn && !params.ack && !params.fin && params.rst)	
		return RESET;
	return BAD_COMMAND;	
}

int numOfConversations(void){
	int counter = 0;
	int i;
	unsigned long now = get_seconds();
	for(i=0;i<CONNECTION_TABLE_ENTRIES;i++){
		if(connTablePtr[i].expires >= now)
			counter++;
	}
	return counter++;
}

void setState(connection_t* conv, tcp_states_t state){
	unsigned long expTime = 0;
	if(state == CONN_SYN_ACK)
		expTime +=5;
	if(state == CONN_ESTAB)
		expTime +=5*60;
	if(state == CLOSE_CLIENT || state == CLOSE_SERVER)
		expTime += 5;
		
	conv->state = state; // update state
	if(expTime != 0){    // update exp time if needed
		expTime += get_seconds();
		conv->expires = expTime;
	}						
}

void clearConnectionTable(void){
	memset(connTablePtr,0,sizeof(connection_t)*CONNECTION_TABLE_ENTRIES);
}
